package com.turtle3d.formallanguage.grammar;

import java.util.ArrayList;
import java.util.Hashtable;

import com.turtle3d.App;
import com.turtle3d.formallanguage.symbol.Method;
import com.turtle3d.formallanguage.symbol.Symbol;
import com.turtle3d.formallanguage.symbol.SymbolClass;
import com.turtle3d.formallanguage.symbol.SymbolFactory;

/**
 * Class generating symbol sequences, basing on productions and axiom.
 * 
 * @author Marek Paterczyk <marek.paterczyk@gmail.com>
 * 
 */
public abstract class Grammar {

	private ArrayList<Symbol> axiom = new ArrayList<Symbol>();
	private Hashtable<Symbol, ProductionFamily> productions = new Hashtable<Symbol, ProductionFamily>();

	/**
	 * Generate symbol sequence.
	 * 
	 * @param iterations
	 *            Number of iterations.
	 * @return ArrayList of Symbols
	 */
	public ArrayList<Symbol> generateSymbolSequence(int iterations) {
		ArrayList<Symbol> symbols = new ArrayList<Symbol>();

		symbols.addAll(axiom);

		for (int iteration = 0; iteration < iterations; iteration++)
			symbols = nextIteration(symbols, iteration);

		return symbols;
	}

	/**
	 * Formats symbol sequence for printing. Good for debbuging purposes.
	 * 
	 * @param symbolSequence
	 *            Symbol sequence that should be converted to String
	 * @param showParameters
	 *            F or F(distance=0.2, radius=0.5, ...)
	 * @return String symbol sequence
	 */
	public static String printSymbolSequence(ArrayList<Symbol> symbolSequence,
			boolean showParameters) {
		String ret = "";
		for (Symbol symbol : symbolSequence) {
			if (showParameters)
				ret += symbol;
			else
				ret += symbol.getSymbolName();
		}

		return ret;
	}

	/**
	 * Formats only symbols of certain type for printing. Good for debbuging
	 * purposes.
	 * 
	 * @param symbolSequence
	 *            Symbol sequence that should be converted to String
	 * @param showParameters
	 *            F or F(distance=0.2, radius=0.5, ...)
	 * @param showOnlyThisSymbol
	 *            Only this symbols will be printed
	 * @return String symbol sequence
	 */
	public static String printSymbolSequence(ArrayList<Symbol> symbolSequence,
			boolean showParameters, String showOnlyThisSymbol) {
		String ret = "";
		for (Symbol symbol : symbolSequence) {
			if (symbol.getSymbolName().equals(showOnlyThisSymbol)) {
				if (showParameters)
					ret += symbol;
				else
					ret += symbol.getSymbolName();
			}

		}

		return ret;
	}

	/**
	 * Draws a production with a given left side symbol
	 * 
	 * @param leftSide
	 *            Left side symbol
	 * @return null if no production with given left side symbol
	 */
	public Production getProduction(Symbol leftSide) {
		return productions.get(leftSide).getProduction();
	}

	/**
	 * This metod calculates next iteration for a given symbol sequence.
	 * Production defined for this Grammar object are used.
	 * 
	 * @param symbolSequence Starting symbolSequence
	 * @param iteration This value is used to set birth iteration for new symbols
	 * @return New symbol sequence
	 */
	public ArrayList<Symbol> nextIteration(ArrayList<Symbol> symbolSequence,
			int iteration) {

		// Output symbol sequence
		ArrayList<Symbol> output = new ArrayList<Symbol>();

		try {
			// Input symbol sequence is iterated
			for (int symbolIndex=0;symbolIndex<symbolSequence.size(); symbolIndex++) {
				Symbol leftSideSymbol = symbolSequence.get(symbolIndex);
				// checks for special "rewrite entire last iteration" symbol !
				// no paramters stuff
				if (leftSideSymbol.getSymbolName().equals("!")) {
					
					// The number of symbols to be skipped
					// This is to ensure that the number of !
					// symbols won't grow with each iteration
					if(leftSideSymbol.parameterExists(Symbol.SKIP_SYMBOLS)){
						//skipping
						int removeIndex = leftSideSymbol.getParameter(Symbol.SKIP_SYMBOLS).intValue();
						symbolIndex += removeIndex;						
					}
										
					Symbol exclamationSymbol = SymbolFactory.getSymbolFactory().createSymbolofClass("!");
					exclamationSymbol.setParameter(Symbol.SKIP_SYMBOLS, symbolSequence.size());
					output.add(exclamationSymbol);					
					output.addAll(symbolSequence);															
				} else {
					// If there is no production with this symbol as left side,
					// the symbol is terminal and rewritten
					if (productions.containsKey(leftSideSymbol)) {
						// This symbol is non-terminal.
						// Extract production						
						Production production = productions.get(leftSideSymbol).getProduction(symbolSequence, symbolIndex);
						
						// Extract production's right side symbol sequence
						// (ArrayList of SymbolClasses)
						if(production==null){
							// just rewrite terminal symbol, 
							// along with all it's parameters unchanged
							output.add(leftSideSymbol);
							continue;
						}
						ArrayList<SymbolClass> rightSide = production.getRightSide();

						// Compute production
						for (int j = 0; j < rightSide.size(); j++) {
							Symbol newSymbol = SymbolFactory.getSymbolFactory()
									.createSymbolofClass(
											rightSide.get(j)
													.getSymbolClassName());
							newSymbol.setBirthIteration(iteration);
							boolean useThisSymbol = true;

							// copy parameters
							newSymbol.copyParametersFromSymbol(leftSideSymbol);

							// Parametric calculations. All methods for this
							// symbol's class are executed.
							for (Method method : newSymbol.getSymbolClass()
									.getMethods()) {
								useThisSymbol = method.calculate(symbolSequence, symbolIndex, newSymbol);
							}

							// Symbol is added or not to output.
							if (useThisSymbol)
								output.add(newSymbol);

						}

					} else {
						// just rewrite terminal symbol, 
						// along with all it's parameters unchanged
						output.add(leftSideSymbol);
					}

				}
			}

			return output;
		} catch (OutOfMemoryError e) {
			App.printlnError("Not enough memory! Symbol sequence partially generated!");
			e.printStackTrace();
			return output;
		}

	}

	/**
	 * Add a defined Production to this grammar
	 * 
	 * @param production
	 *            Production to add
	 */
	public void addProduction(Production production) {
		if (productions.get(production.getLeftSide()) == null)
			productions.put(production.getLeftSide(), new ProductionFamily(
					production.getLeftSide()));

		productions.get(production.getLeftSide()).addProduction(production);
	}

	/**
	 * Gives grammar's name. It is the name of the class.
	 * 
	 * @return grammar's name
	 */
	public String getName() {
		return this.getClass().getSimpleName();
	}

	/**
	 * Method for setting a required paramter and it's value
	 * 
	 * @param name
	 *            parameter name
	 * @param value
	 *            parameter value
	 */
	public void setRequiredParameter(String name, Float value) {
		getParameters().put(name, value);
	}

	/**
	 * Get all defined paramters.
	 * 
	 * @return Hashtable with key-value paramter pairs
	 */
	public Hashtable<String, Float> getParameters() {
		return SymbolFactory.getSymbolFactory().getDefaultParameters();
	}

	/**
	 * Defines the grammar and resets SymbolFactory.
	 */
	public final void init() {
		SymbolFactory.reset();

		axiom = new ArrayList<Symbol>();
		productions = new Hashtable<Symbol, ProductionFamily>();

		define();
		setRequiredParameters();
	}

	/**
	 * Grammar definition goes here. Call init() to define the grammar.
	 * 
	 */
	public void define() {
		App.printlnError("Grammar not defined!");
	}

	/**
	 * Define required parameters here
	 */
	public void setRequiredParameters() {

	}

	/**
	 * Get axiom.
	 * 
	 * @return axiom
	 */
	public ArrayList<Symbol> getAxiom() {
		return axiom;
	}

	/**
	 * Set axiom.
	 * 
	 * @param axiom
	 *            axiom (sequence)
	 */
	public void setAxiom(ArrayList<Symbol> axiom) {
		this.axiom = axiom;
	}

	/**
	 * Set axiom
	 * 
	 * @param symbol
	 *            axiom (one symbol)
	 */
	public void setAxiom(Symbol symbol) {
		axiom.add(symbol);
	}

}
